import { Tabs, Steps, Callout } from "nextra/components";
import UniversalTabs from "../../components/UniversalTabs";

# Kubernetes Deployment via Glasskube

## Prerequisites

- A Kubernetes cluster currently set as the current context in `kubectl`
- `docker`, `openssl`, `kubectl` and [`glasskube`](https://glasskube.dev) installed

## What is Glasskube?

[Glasskube](https://glasskube.dev) is an alternative package manager for Kubernetes and part of the CNCF landscape. Glasskube is designed as a Cloud Native application and every installed package is represented by a Custom Resource.

<Callout type="info" emoji="🧊">

[`glasskube/glasskube`](https://github.com/glasskube/glasskube/) is in active development, with _good first issues_
available for new contributors.

</Callout>

## Quickstart

<Steps>
### Generate encryption keys

There are 4 encryption secrets required for Hatchet to run which can be generated via the following bash script (requires `docker` and `openssl`):

```sh filename=generate.sh copy
#!/bin/bash

# Define an alias for generating random strings. This needs to be a function in a script.
randstring() {
    openssl rand -base64 69 | tr -d "\n=+/" | cut -c1-$1
}

# Create keys directory
mkdir -p ./keys

# Function to clean up the keys directory
cleanup() {
    rm -rf ./keys
}

# Register the cleanup function to be called on the EXIT signal
trap cleanup EXIT

# Check if Docker is installed
if ! command -v docker &> /dev/null
then
    echo "Docker could not be found. Please install Docker."
    exit 1
fi

# Generate keysets using Docker
docker run --user $(id -u):$(id -g) -v $(pwd)/keys:/hatchet/keys ghcr.io/hatchet-dev/hatchet/hatchet-admin:latest /hatchet/hatchet-admin keyset create-local-keys --key-dir /hatchet/keys

# Read keysets from files
SERVER_ENCRYPTION_MASTER_KEYSET=$(<./keys/master.key)
SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET=$(<./keys/private_ec256.key)
SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET=$(<./keys/public_ec256.key)

# Generate the random strings for SERVER_AUTH_COOKIE_SECRETS
SERVER_AUTH_COOKIE_SECRET1=$(randstring 16)
SERVER_AUTH_COOKIE_SECRET2=$(randstring 16)

# Create the YAML file
cat > hatchet-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: hatchet-secret
  namespace: hatchet
stringData:
  SERVER_AUTH_COOKIE_SECRETS: "$SERVER_AUTH_COOKIE_SECRET1 $SERVER_AUTH_COOKIE_SECRET2"
  SERVER_ENCRYPTION_MASTER_KEYSET: "$SERVER_ENCRYPTION_MASTER_KEYSET"
  SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET: "$SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET"
  SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET: "$SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET"
EOF
```

### Apply the secret

To apply the secret run:

```sh
kubectl create namespace hatchet
kubectl apply -f hatchet-secret.yaml
```

### Install Hatchet

<Tabs items={['CLI', 'GUI']}>
    <Tabs.Tab>

        ```sh
        glasskube install hatchet
        ```

        Make sure to set the api server and engine replica count to 0 and use the secret name you applied before (`hatchet-secret`).

    </Tabs.Tab>
    <Tabs.Tab>

        ```sh
        glasskube serve
        ```

        You can now install Hatchet via the GUI. Make sure to follow the instructions in the UI.

    </Tabs.Tab>

</Tabs>

### Open Hatchet

<Tabs items={['CLI', 'GUI']}>
    <Tabs.Tab>
        ```sh
        glasskube open hatchet
        ```

    </Tabs.Tab>
    <Tabs.Tab>

       You can simply use the "Open" button in the GUI.

    </Tabs.Tab>

</Tabs>

### Set up your Hatchet account

You can create a new user account by clicking the `Register` button on the Hatchet login screen, and then creating a new account and tenant.

Next, navigate to your settings tab in the Hatchet dashboard. You should see a section called "API Keys". Click "Create API Key", input a name for the key and copy the key. Then copy the following environment variable:

```
HATCHET_CLIENT_TOKEN="<token>"
HATCHET_CLIENT_TLS_STRATEGY=none
```

You will need this in the following example.

### Port forward to the Hatchet engine

```sh
export NAMESPACE=hatchet # TODO: change if you modified the namespace
export POD_NAME=$(kubectl get pods --namespace $NAMESPACE -l "app.kubernetes.io/name=hatchet-engine,app.kubernetes.io/instance=hatchet" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace $NAMESPACE $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
kubectl --namespace $NAMESPACE port-forward $POD_NAME 7070:$CONTAINER_PORT
```

This will spin up the Hatchet engine service on `localhost:7070` which you can then connect to from the examples.

### Run your first worker

<UniversalTabs items={['Python', 'Typescript', 'Go']}>
  <Tabs.Tab>
Make sure you have the following dependencies installed:

```sh
pip install python-dotenv
pip install hatchet-sdk
```

We are using [`python-dotenv`](https://pypi.org/project/python-dotenv/) to load the environment variables from a `.env` file. This isn't required, and you can use your own method to load environment variables.

Create a `worker.py` file with the following contents:

```python filename="worker.py" copy
from hatchet_sdk import Hatchet
from dotenv import load_dotenv

load_dotenv()

hatchet = Hatchet(debug=True)

@hatchet.workflow(name="first-python-workflow",on_events=["user:create"])
class MyWorkflow:
    @hatchet.step()
    def step1(self, context):
        return {
            "result": "success"
        }

worker = hatchet.worker('first-worker')
worker.register_workflow(MyWorkflow())

worker.start()
```

Open a new terminal and start the worker with:

```sh
python3 worker.py
```

  </Tabs.Tab>
  <Tabs.Tab>
First, install `@hatchet-dev/typescript-sdk` via:

```sh npm2yarn
npm i @hatchet-dev/typescript-sdk
npm i dotenv
```

We also use `dotenv` to load the environment variables from a `.env` file. This isn't required, and you can use your own method to load environment variables.

Next, copy the following code into a `worker.ts` file:

```typescript filename="worker.ts" copy
import Hatchet, { Workflow } from "@hatchet-dev/typescript-sdk";
import dotenv from "dotenv";

dotenv.config();

const hatchet = Hatchet.init();

const workflow: Workflow = {
  id: "first-typescript-workflow",
  description: "This is my first workflow",
  on: {
    event: "user:create",
  },
  steps: [
    {
      name: "step1",
      run: async (ctx) => {
        console.log(
          "starting step1 with the following input",
          ctx.workflowInput(),
        );

        return {
          result: "success!",
        };
      },
    },
  ],
};

const worker = hatchet.worker("my-worker");
await worker.registerWorkflow(workflow);
worker.start();
```

Next, modify your `package.json` to include a script to start:

```json
{
  // ...rest of your `package.json`
  "scripts": {
    // ...existing scripts
    "worker": "npx ts-node worker.ts"
  }
}
```

Now to start the worker, in a new terminal run:

```sh npm2yarn
npm run worker
```

  </Tabs.Tab>
  <Tabs.Tab>
  In a new Go project, copy the following code into a `main.go` file:

```go filename="main.go" copy
package main

import (
	"fmt"

	"github.com/joho/godotenv"

	"github.com/hatchet-dev/hatchet/pkg/client"
	"github.com/hatchet-dev/hatchet/pkg/cmdutils"
	"github.com/hatchet-dev/hatchet/pkg/worker"
)

type stepOutput struct{}

func main() {
	err := godotenv.Load()
	if err != nil {
		panic(err)
	}

	c, err := client.New()

	if err != nil {
		panic(fmt.Sprintf("error creating client: %v", err))
	}

	w, err := worker.NewWorker(
		worker.WithClient(
			c,
		),
		worker.WithMaxRuns(1),
	)
	if err != nil {
		panic(fmt.Sprintf("error creating worker: %v", err))
	}

	err = w.RegisterWorkflow(
		&worker.WorkflowJob{
			Name:        "simple-workflow",
			Description: "Simple one-step workflow.",
      On:          worker.Events("simple"),
			Steps: []*worker.WorkflowStep{
				worker.Fn(func(ctx worker.HatchetContext) (result *stepOutput, err error) {
					fmt.Println("executed step 1")

					return &stepOutput{}, nil
				},
				).SetName("step-one"),
			},
		},
	)
	if err != nil {
		panic(fmt.Sprintf("error registering workflow: %v", err))
	}

	interruptCtx, cancel := cmdutils.InterruptContextFromChan(cmdutils.InterruptChan())
	defer cancel()

	cleanup, err := w.Start()
	if err != nil {
		panic(fmt.Sprintf("error starting worker: %v", err))
	}

	<-interruptCtx.Done()
	if err := cleanup(); err != nil {
		panic(err)
	}
}
```

Next, run the following command to start the worker:

```sh
go run main.go
```

</Tabs.Tab>
</UniversalTabs>

### Run your first workflow

The worker is now running and listening for steps to execute. You should see your first worker registered in the `Workers` tab of the Hatchet dashboard:

![Quickstart 1](/quickstart-1.png)

You can now trigger your first workflow by navigating to the `Workflows` tab, selecting your workflow, and clicking the top right "Trigger workflow" button:

![Quickstart 2](/quickstart-2.png)

That's it! You've successfully deployed Hatchet and run your first workflow.

</Steps>
